// Main.cpp : The Entry Point
//

#include "stdafx.h"
#include "Singleton.h"
#include "ThreadStarter.h"
#include "ThreadPool.h"
#include "PeriodicFunctionCaller_thread.h"
#include "../tvm/tvm.h"
#include "../tvm/tvm_limits.h"
#include "../tvm/tvm_utility.h"
#include "../tvm/tvm_define.h"
#include "../tvm/tvm_instruction.h"
#include <iostream>
#include <fstream>
using namespace std;

static uint8*				code;
static uint32				period;
static volatile bool	haltSignal;
static uint32				autoLog;

class TVM : public Singleton <TVM>
{
public:
	//callback function
	void TVM_Callback()
	{
		uint32 tvmRet = TVM_Run();
		if (tvmRet == TVM_RET_ERROR || tvmRet == TVM_RET_HALTED)
		{
			haltSignal = true;
		}
		if (tvmRet == TVM_RET_PAUSED)
		{
			TVM_Resume();
		}

		if(tvmRet == TVM_RET_WAITING)
		{
			//to simulate that the event has been triggered
			for(size_t i = EVENT_SLOT_MIN; i <= EVENT_SLOT_MAX; ++i)
			{
				TVM_TriggerEvent(i);
			}
		}
	}
};

initialiseSingleton(TVM);

/*callback function to send the key*/
void SendKey(size_type key)
{
	if (autoLog != TVM_AUTO_LOG_ALL)
	{
		wcout<<"sending key "<<key<<endl;
	}
}

void Log(const char* msg)
{
	wcout<<msg;
}

size_t GetFileSize(fstream&fstr)
{
	//move to the end
	fstr.seekg(0, ios::end);
	//get the size of the file
	size_t size = fstr.tellg();
	//return to the beginning
	fstr.seekg(ios::beg);
	return size;
}

bool TVMInit(const _TCHAR* tataFilename)
{
	fstream tataFile(tataFilename, ios::in | ios::binary);
	if (!tataFile)
	{
		wcerr<<"TVM Simulator Error: initialization failed. can't open the file "<<tataFilename<<"!"<<endl;
		return false;
	}

	size_t size = GetFileSize(tataFile);

	if (size < TVM_MIN_SIZE || size > TVM_MAX_SIZE)
	{
		wcerr<<"TVM Simulator Error: initialization failed. invalid tata bin file size."<<endl;
		return false;
	}

	code = new uint8[size];
	if (code == 0)
	{
		wcerr<<"TVM Simulator Error: out of memory"<<endl;
		return false;
	}

	for (size_t i = 0; i < size; ++i)
	{
		code[i] = tataFile.get();
	}

	period = code[TVM_PERIOD_FIELD_OFFSET];

	uint32 option;
	GetBitRange(code[TVM_VERSION_FIELD_OFFSET], 
		TVM_OPTION_BIT_START, TVM_OPTION_BIT_END, &option);
	GetBitRange(option, TVM_AUTO_LOG_START_BIT, TVM_AUTO_LOG_END_BIT, &autoLog);

	TVM_Init(code, static_cast<uint32>(size), SendKey, Log);

	//check if any error happened
	if (TVM_GetLastError() != TVM_ERROR_OK)
	{
		wcerr<<"TVM Simulator Error: TVM initialization failed!"<<endl;
		return false;
	}

	haltSignal = false;
	return true;
}

void TVMClean()
{
	delete []code;
}

void Usage()
{
	wcerr<<"Usage: tvms filename\neg: tvms tata.tfy"<<endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
	if (argc != 2)
	{
		wcerr<<"TVM Simulator Error: No enough argument or too many argument."<<endl;
		Usage();
		return -1;
	}

	ThreadPool.Startup();
	::Sleep(1000);

	wcout<<"Starting TVM Simulator"<<endl<<endl;
	if (TVMInit(argv[1]) != true)
	{
		return -1;
	}

	NoArgPeriodicFunctionCaller<TVM>* pfc;
	pfc = new NoArgPeriodicFunctionCaller<TVM>(TVM::GetSingletonPtr(),
		&TVM::TVM_Callback,
		period);

	ThreadPool.ExecuteTask(pfc);

	while(haltSignal == false)//wait for the signal
	{
		;
	}

	pfc->Kill();
	TVMClean();
	wcout<<endl<<"TVM Simulator Exited"<<endl;
	::Sleep(1000);
	ThreadPool.Shutdown();
	delete	pfc;
	pfc	=	0;

	return 0;
}
